﻿namespace EFS.RC.CC.SAL.Loggers
{
	/// Class Logger
	public sealed class Logger : System.IDisposable
	{
		#region Composition

		// log messages
		private System.Collections.Generic.List<EFS.RC.CC.SAL.Loggers.Messages.Message> items = new System.Collections.Generic.List<EFS.RC.CC.SAL.Loggers.Messages.Message>();

		// writters
		private System.Collections.Generic.List<EFS.RC.CC.SAL.Writers.IWriter> writers = new System.Collections.Generic.List<EFS.RC.CC.SAL.Writers.IWriter>();

		// sync access
		private System.Object syncAccess = new System.Object();

		// wait start
		private System.Threading.ManualResetEvent waitStartEvent = null;

		// wait stop
		private System.Threading.ManualResetEvent waitStopEvent = null;

		// wait write
		private System.Threading.ManualResetEvent waitWriteEvent = null;

		// stop flag
		private System.Int32 stop = 0;

		// log thread
		private System.Threading.Thread logThread = null;

		#endregion

		#region Constructors
		
		/// <summary>
		/// Static constructor to init local instance
		/// </summary>
		static Logger()
		{
			Instance = new Logger();
		}

		/// <summary>
		/// Contructor
		/// </summary>
		private Logger()
		{
		}

		#endregion

		#region Usage

		/// Property Instance (singleton): Get
		public static Logger Instance { get; private set; }

		/// <summary>
		/// Check if writter is active
		/// </summary>
		public System.Boolean IsWritterActive
		{
			get
			{
				// lock and get state
				// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
				lock (syncAccess)
				{
					return logThread.ThreadState == System.Threading.ThreadState.Running;
				}
			}
		}

		/// Adds a message
		public void AddWriter<T>(T writer) where T : EFS.RC.CC.SAL.Writers.IWriter 
		{
			// check
			EFS.Common.Verify.Reference(writer, "writer");

			// add to collection
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace()); lock (syncAccess)
			lock (syncAccess)
			{
				// check not in
				if (writers.Contains(writer))
				{
 					// error
					throw new System.InvalidOperationException(System.String.Format("Writer already added to collection {0}", writer.Describe()));
				}

				// register for terminate
				writer.RegisterTerminate<T>(WriterTerminated);

				// check
				writers.Add(writer);
			}
		}

		/// Adds a message
		public void Add(EFS.RC.CC.SAL.Loggers.Messages.Message messageObj)
		{
			// check enabled
			if (!EFS.RC.CC.SAL.Config.Settings.Enabled)
			{
				return;
			}

			// add to collection
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace()); lock (syncAccess)
			lock (syncAccess)
			{
				// check
				if (null == logThread)
				{
					throw new System.InvalidOperationException("SAL not started");
				}

				// add
				items.Add(messageObj);
			}

			// signal new message
			waitWriteEvent.Set();
		}

		/// <summary>
		/// Start writter, wait to start
		/// </summary>
		public void StartWritter()
		{
			// only one can start log
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace()); lock (syncAccess)
			{
				// must not have thread
				if (null != logThread)
				{
					throw new System.InvalidOperationException("SAL thread already started");
				}

				// reset stop
				stop = 0;

				// sanity check
				EFS.Common.Verify.Condition(null == waitStartEvent, "SAL Unexpected state waitStartEvent != null");

				// make event
				waitStartEvent = new System.Threading.ManualResetEvent(false);

				// must be null
				EFS.Common.Verify.Condition(null == waitWriteEvent, "SAL Wrong waitWriteEvent state: should be null");

				// create new wait event
				waitWriteEvent = new System.Threading.ManualResetEvent(false);

				// create new thread
				logThread = new System.Threading.Thread(new System.Threading.ThreadStart(LogThreadProc));
				logThread.Start();

				// wait start
				waitStartEvent.WaitOne();

				// release
				waitStartEvent.Close();

				// clean
				waitStartEvent = null;
			}
		}

		/// Stop
		public void StopWritter()
		{
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// must have thread
				if (null == logThread)
				{
					throw new System.InvalidOperationException("SAL thread not started");
				}

				// check state
				if (logThread.ThreadState == System.Threading.ThreadState.Running ||
					logThread.ThreadState == System.Threading.ThreadState.WaitSleepJoin)
				{
					// sanity check
					EFS.Common.Verify.Condition(null == waitStopEvent, "SAL Unexpected state waitStopEvent != null");

					// make event
					waitStopEvent = new System.Threading.ManualResetEvent(false);

					// set signal to thread
					stop = 1;

					// create new wait event
					waitWriteEvent.Set();

					// wait stop
					if (waitStopEvent.WaitOne(500))
					{
						// abort
						logThread.Abort();
					}

					// release
					waitStopEvent.Close();

					// clean
					waitStopEvent = null;
				}

				// let GC take care of it
				logThread = null;
			}
		}

		#endregion

		#region Runtime

		/// <summary>
		/// terminated
		/// </summary>
		/// <param name="writer"></param>
		private void WriterTerminated<T>(T writer) where T : EFS.RC.CC.SAL.Writers.IWriter
		{
			// check
			EFS.Common.Verify.Reference(writer, "writer");

			// lock
			lock (syncAccess)
			{
				writers.Remove(writer);
			}
		}

		/// Working thread procedure
		private void LogThreadProc()
		{
			// check if some one is wating
			if (null != waitStartEvent)
			{
				// wait set
				waitStartEvent.Set();
			}

			// cicle
			while (stop != 1)
			{
				try
				{
					// temp items
					System.Collections.Generic.List<EFS.RC.CC.SAL.Loggers.Messages.Message> itemsTemp = null;

					// add to collection locked while we swith
					// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
					lock (syncAccess)
					{
						// not more than maximum allowed
						if (items.Count > EFS.RC.CC.SAL.Config.Settings.MaximumItemsQueued)
						{
							// create error & log
							System.Text.StringBuilder sbWarning = new System.Text.StringBuilder();
							sbWarning.AppendFormat("Logger: queue count, {0}, exceeded maximum allowed, {1}. Older dropped.", items.Count, EFS.RC.CC.SAL.Config.Settings.MaximumItemsQueued);
							
							// log
							EFS.EventLog.Log.LogWarning(sbWarning.ToString(), null);

							// drop older
							items.RemoveRange(0, items.Count - EFS.RC.CC.SAL.Config.Settings.MaximumItemsQueued);
						}

						// swap array
						itemsTemp = items;
						items = new System.Collections.Generic.List<EFS.RC.CC.SAL.Loggers.Messages.Message>();
					}

					// cicle all and write to log
					foreach (EFS.RC.CC.SAL.Loggers.Messages.Message message in itemsTemp)
					{
						// Write log
						EFS.RC.CC.SAL.Writers.DB.Instance.Write(message);

						// external writers
						foreach (EFS.RC.CC.SAL.Writers.IWriter writer in writers)
						{
							writer.Write(message);
						}
					}

					// test if we must wait since no activity
					if (0 == itemsTemp.Count && stop != 1)
					{
						// Wait a Set
						waitWriteEvent.WaitOne();

						// Reset for next wait
						waitWriteEvent.Reset();
					}
				}
				catch (System.Threading.ThreadAbortException)
				{
					// bye
					break;
				}
				catch (System.Exception ex)
				{
					EFS.EventLog.Log.LogException(ex, null);
				}
			}

			// check if some one is wating
			if (null != waitStopEvent)
			{
				// wait set
				waitStopEvent.Set();
			}
		}

		#endregion

		#region Destructor

		/// <summary>
		/// Dispose
		/// </summary>
		public void Dispose()
		{
			try
			{
				if (waitStartEvent != null)
				{
					waitStartEvent.Close();
					waitStartEvent = null;
				}

				if (waitStopEvent != null)
				{
					waitStopEvent.Close();
					waitStopEvent = null;
				}

				if (waitWriteEvent != null)
				{
					waitWriteEvent.Close();
					waitWriteEvent = null;
				}
			}
			catch (System.Exception ex)
			{
				EFS.EventLog.Log.LogException(ex, null);
			}
		}

		/// <summary>
		/// Destructor
		/// </summary>
		~Logger()
		{
			Dispose();
		}

		#endregion
	}

}